{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "nbsphinx": "hidden"
   },
   "source": [
    "This notebook is part of the `clifford` documentation: https://clifford.readthedocs.io/."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example 2 Clustering Geometric Objects"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this example we will look at a few of the tools provided by the clifford package for (4,1) conformal geometric algebra (CGA) and see how we can use them in a practical setting to cluster geometric objects via the simple K-means clustering algorithm provided in clifford.tools\n",
    "\n",
    "As before the first step in using the package for CGA is to generate and import the algebra:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.g3c import *\n",
    "print('e1*e1 ', e1*e1)\n",
    "print('e2*e2 ', e2*e2)\n",
    "print('e3*e3 ', e3*e3)\n",
    "print('e4*e4 ', e4*e4)\n",
    "print('e5*e5 ', e5*e5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The tools submodule of the clifford package contains a wide array of algorithms and tools that can be useful for manipulating objects in CGA. In this case we will be generating a large number of objects and then segmenting them into clusters.\n",
    "\n",
    "We first need an algorithm for generating a cluster of objects in space. We will construct this cluster by generating a random object and then repeatedly disturbing this object by some small fixed amount and storing the result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.tools.g3c import *\n",
    "import numpy as np\n",
    "\n",
    "def generate_random_object_cluster(n_objects, object_generator, max_cluster_trans=1.0, max_cluster_rot=np.pi/8):\n",
    "    \"\"\" Creates a cluster of random objects \"\"\"\n",
    "    ref_obj = object_generator()\n",
    "    cluster_objects = []\n",
    "    for i in range(n_objects):\n",
    "        r = random_rotation_translation_rotor(maximum_translation=max_cluster_trans, maximum_angle=max_cluster_rot)\n",
    "        new_obj = apply_rotor(ref_obj, r)\n",
    "        cluster_objects.append(new_obj)\n",
    "    return cluster_objects"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can use this function to create a cluster and then we can visualise this cluster with [pyganja](https://github.com/pygae/pyganja)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyganja import *\n",
    "clustered_circles = generate_random_object_cluster(10, random_circle)\n",
    "sc = GanjaScene()\n",
    "for c in clustered_circles:\n",
    "    sc.add_object(c, rgb2hex([255,0,0]))\n",
    "draw(sc, scale=0.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This cluster generation function appears in clifford tools by default and it can be imported as follows:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.tools.g3c import generate_random_object_cluster"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we can generate individual clusters we would like to generate many:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster ):\n",
    "    object_clusters = []\n",
    "    for i in range(n_clusters):\n",
    "        cluster_objects = generate_random_object_cluster(n_objects_per_cluster, object_generator,\n",
    "                                                         max_cluster_trans=0.5, max_cluster_rot=np.pi / 16)\n",
    "        object_clusters.append(cluster_objects)\n",
    "    all_objects = [item for sublist in object_clusters for item in sublist]\n",
    "    return all_objects, object_clusters"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Again this function appears by default in clifford tools and we can easily visualise the result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.tools.g3c import generate_n_clusters\n",
    "\n",
    "all_objects, object_clusters = generate_n_clusters(random_circle, 2, 5)\n",
    "sc = GanjaScene()\n",
    "for c in all_objects:\n",
    "    sc.add_object(c, rgb2hex([255,0,0]))\n",
    "draw(sc, scale=0.05)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Given that we can now generate multiple clusters of objects we can test algorithms for segmenting them.\n",
    "\n",
    "The function run_n_clusters below generates a lot of objects distributed into n clusters and then attempts to segment the objects to recover the clusters. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from clifford.tools.g3c.object_clustering import n_clusters_objects\n",
    "import time\n",
    "\n",
    "def run_n_clusters( object_generator, n_clusters, n_objects_per_cluster, n_shotgunning):\n",
    "    all_objects, object_clusters = generate_n_clusters( object_generator, n_clusters, n_objects_per_cluster )     \n",
    "    [new_labels, centroids, start_labels, start_centroids] = n_clusters_objects(n_clusters, all_objects,          \n",
    "                                                                                initial_centroids=None,           \n",
    "                                                                                n_shotgunning=n_shotgunning,      \n",
    "                                                                                averaging_method='unweighted')    \n",
    "    return all_objects, new_labels, centroids                                                                     "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lets try it!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def visualise_n_clusters(all_objects, centroids, labels,\n",
    "                         color_1=np.array([255, 0, 0]), \n",
    "                         color_2=np.array([0, 255, 0])):\n",
    "    \"\"\"\n",
    "    Utility method for visualising several clusters and their respective centroids\n",
    "    using pyganja\n",
    "    \"\"\"\n",
    "    alpha_list = np.linspace(0, 1, num=len(centroids))\n",
    "    sc = GanjaScene()\n",
    "    for ind, this_obj in enumerate(all_objects):\n",
    "        alpha = alpha_list[labels[ind]]\n",
    "        cluster_color = (alpha * color_1 + (1 - alpha) * color_2).astype(np.int)\n",
    "        sc.add_object(this_obj, rgb2hex(cluster_color))\n",
    "\n",
    "    for c in centroids:\n",
    "        sc.add_object(c, Color.BLACK)\n",
    "\n",
    "    return sc\n",
    "\n",
    " \n",
    "\n",
    "object_generator = random_circle     \n",
    "\n",
    "n_clusters = 3                                                                            \n",
    "n_objects_per_cluster = 10                                                                \n",
    "n_shotgunning = 60                                                                        \n",
    "all_objects, labels, centroids = run_n_clusters(object_generator, n_clusters,        \n",
    "                                                     n_objects_per_cluster, n_shotgunning)\n",
    "                                                                                          \n",
    "sc = visualise_n_clusters(all_objects, centroids, labels,       \n",
    "                          color_1=np.array([255, 0, 0]), \n",
    "                          color_2=np.array([0, 255, 0]))        \n",
    "draw(sc, scale=0.05)                                                                                 "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
